package queries;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;

import javax.xml.parsers.ParserConfigurationException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xml.sax.SAXException;

import util.Logger;
import util.NestedRuntimeException;
import util.WebUtils;
import engine.QueryRefiner;

/**
 * Query execution using Yahoo BOOS API
 * 
 * @author Vika
 * @author ak2765
 */
public final class YahooQueryExecutor {

	private static final String BOSS_KEY = "9BvTPxLV34Gs8Dfbd2VVlOPh.sOaW_nyaj_niU7_uF2ELidyfsgP18u_gQ5K3Xz8W2yxnXiyu0Ly8EvgZp_MY2Dz41vSzOM-";

	private Query currentQuery;

	private QueryResult currentQueryResult;
	
	private QueryRefiner queryRefiner = new QueryRefiner();
	
	public QueryRefiner getQueryRefiner() {
		return queryRefiner;
	}

	public YahooQueryExecutor(Query query) {
		this.currentQuery = query;
	}

	/**
	 * Execute query after relevance feedback
	 * 
	 * @param selectedDocuments
	 * @return
	 */
	public QueryResult executeNextRound(final int[] selectedDocuments) {
		this.currentQuery = queryRefiner.refineQuery(this.currentQuery, this.currentQueryResult, selectedDocuments);
		Logger.info("Refined query: " + this.currentQuery);
		
		this.currentQueryResult = this.doExecuteQuery(this.currentQuery);
		
		return this.currentQueryResult;
	}

	/**
	 * Execute initial query
	 * 
	 * @return
	 */
	public QueryResult executeInitialRound() {
		this.currentQueryResult = this.doExecuteQuery(currentQuery);
		
		return this.currentQueryResult;
	}

	/**
	 * Query execution with BOSS
	 * 
	 * @param query
	 * @return
	 */
	private QueryResult doExecuteQuery(Query query) {
		try {
			String encodedQuery = URLEncoder.encode(query.nativeStringQuery(),
					"UTF-8");

			URL url = new URL("http://boss.yahooapis.com/ysearch/web/v1/"
					+ encodedQuery + "?appid=" + BOSS_KEY
					+ "&count=10&format=json");

			URLConnection connection = url.openConnection();

			String bossResponseXml = readBossResponse(connection);

			return parseBossResponseXml(bossResponseXml);
		} catch (Exception ex) {
			String errMsg = String.format("Failed query: %s", query.toString());
			throw new NestedRuntimeException(errMsg, ex);
		}
	}

	/**
	 * Parse search results in XML format
	 * 
	 * @param bossResponseXml
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws JSONException
	 */
	private QueryResult parseBossResponseXml(String bossResponseXml)
			throws ParserConfigurationException, SAXException, IOException,
			JSONException {

		QueryResult queryResult = new QueryResult();

		JSONObject jobj = new JSONObject(bossResponseXml)
				.getJSONObject("ysearchresponse");

		String nextPage = jobj.has("nextpage") ? jobj.getString("nextpage")
				: null;
		queryResult.setNextResultUrl(nextPage);

		if (jobj.has("resultset_web")) {
			JSONArray res = jobj.getJSONArray("resultset_web");
			for (int i = 0; i < res.length(); i++) {
				JSONObject thisResult = res.getJSONObject(i);

				queries.Document document = new queries.Document();

				document.setDescription(WebUtils.removeHtmlTags(thisResult
						.getString("abstract")));

				document.setTitle(WebUtils.removeHtmlTags(thisResult.getString("title")));

				document.setUrl(thisResult.getString("url"));

				queryResult.addDocument(document);
			}
		}

		return queryResult;
	}

	/**
	 * Read search results
	 * 
	 * @param connection
	 * @return
	 * @throws IOException
	 */
	private String readBossResponse(URLConnection connection)
			throws IOException {
		String line;
		StringBuilder builder = new StringBuilder();
		BufferedReader reader = new BufferedReader(new InputStreamReader(
				connection.getInputStream()));
		while ((line = reader.readLine()) != null) {
			builder.append(line);
		}
		return builder.toString();
	}

}
